/**
 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <cstdint>
#include "compiler_logger.h"
#include "optimizer/ir_builder/inst_builder.h"
#include "optimizer/ir_builder/ir_builder.h"
#include "optimizer/ir/inst.h"
#include "bytecode_instruction.h"
#include "bytecode_instruction-inl.h"
#include "include/coretypes/tagged_value.h"
#include "optimizer/ir_builder/pbc_iterator.h"
namespace ark::compiler {

void InstBuilder::FinalizeEcmaFnCall(SaveStateInst *saveState, CallInst *callInst)
{
    callInst->AppendInput(saveState);
    callInst->AddInputType(DataType::NO_TYPE);
    AddInstruction(callInst);
    UpdateDefinitionAcc(callInst);
}

// NOLINTNEXTLINE(misc-definitions-in-headers)
void InstBuilder::BuildEcmaFnCall(const BytecodeInstruction *bcInst, bool isRange, bool callThis, uint64_t numArgs)
{
    auto bcPc = GetPc(bcInst->GetAddress());
    auto saveState = CreateSaveState(Opcode::SaveState, bcPc);
    AddInstruction(saveState);

    Inst *callee {};

    switch (bcInst->GetOpcode()) {
        case BytecodeInstruction::Opcode::ECMA_CALL0DYN_PREF_NONE_PROF16: {
            callee = GetDefinitionAcc();
            break;
        }
        default: {
            callee = GetDefinition(bcInst->GetVReg(0));
            break;
        }
    }

    // Check callee is heap object
    auto typeCheck = BuildAnyTypeCheckInst(bcPc, callee, saveState, AnyBaseType::ECMASCRIPT_HEAP_OBJECT_TYPE);
    InitAnyTypeCheckInst(typeCheck);
    callee = typeCheck;

    {  // Load class (it is movable object in js)
        auto loadClass = GetGraph()->CreateInstLoadObject(
            DataType::REFERENCE, bcPc, callee, TypeIdMixin {TypeIdMixin::MEM_DYN_CLASS_ID, GetGraph()->GetMethod()},
            nullptr);
        loadClass->SetObjectType(ObjectType::MEM_DYN_CLASS);
        AddInstruction(loadClass);

        // Load Jshclass
        auto loadHclass = GetGraph()->CreateInstLoadObject(
            DataType::REFERENCE, bcPc, loadClass, TypeIdMixin {TypeIdMixin::MEM_DYN_HCLASS_ID, GetGraph()->GetMethod()},
            nullptr);
        loadHclass->SetObjectType(ObjectType::MEM_DYN_HCLASS);
        AddInstruction(loadHclass);

        auto hclassCheck = GetGraph()->CreateInstHclassCheck(DataType::NO_TYPE, bcPc, loadHclass, saveState);
        hclassCheck->SetCheckIsFunction(true);
        hclassCheck->SetCheckFunctionIsNotClassConstructor(true);
        AddInstruction(hclassCheck);
    }

    auto callInst = GetGraph()->CreateInstCallDynamic(DataType::ANY, bcPc);
    callInst->SetCanNativeException(true);

    // func, new_target, this?, args..., ss
    uint64_t argsCount = 2U + (callThis ? 0 : 1) + (isRange ? bcInst->GetImm64() : numArgs) + 1U;
    callInst->ReserveInputs(argsCount);
    callInst->AllocateInputTypes(GetGraph()->GetAllocator(), argsCount);

    // Append func, new_target, this
    callInst->AppendInput(callee);
    callInst->AddInputType(DataType::ANY);
    callInst->AppendInput(FindOrCreateAnyConstant(DataType::Any(coretypes::TaggedValue::VALUE_UNDEFINED)));
    callInst->AddInputType(DataType::ANY);

    if (!callThis) {  // Assume strict mode
        callInst->AppendInput(FindOrCreateAnyConstant(DataType::Any(coretypes::TaggedValue::VALUE_UNDEFINED)));
        callInst->AddInputType(DataType::ANY);
    } else {
        numArgs++;
    }

    switch (bcInst->GetOpcode()) {
        case BytecodeInstruction::Opcode::ECMA_CALL0DYN_PREF_NONE_PROF16: {
            FinalizeEcmaFnCall(saveState, callInst);
            return;
        }
        case BytecodeInstruction::Opcode::ECMA_CALLIRANGEDYN_PREF_IMM16_V8_PROF16:
        case BytecodeInstruction::Opcode::ECMA_CALLITHISRANGEDYN_PREF_IMM16_V8_PROF16: {
            auto rangeStart = bcInst->GetVReg(0) + 1;
            auto rangeSize = bcInst->GetImm64();

            for (int64_t i = 0; i < rangeSize; ++i) {
                callInst->AppendInput(GetDefinition(rangeStart + i));
                callInst->AddInputType(DataType::ANY);
            }

            FinalizeEcmaFnCall(saveState, callInst);
            return;
        }
        default: {
            break;
        }
    }

    for (uint64_t i = 1; i < numArgs; ++i) {
        callInst->AppendInput(GetDefinition(bcInst->GetVReg(i)));
        callInst->AddInputType(DataType::ANY);
    }

    callInst->AppendInput(GetDefinitionAcc());
    callInst->AddInputType(DataType::ANY);

    FinalizeEcmaFnCall(saveState, callInst);
}

void InstBuilder::BuildEcmaNewobjdynrange(const BytecodeInstruction *bcInst)
{
    auto graph = GetGraph();
    auto bcPc = GetPc(bcInst->GetAddress());
    auto startReg = bcInst->GetVReg(0);

    auto inst = graph->CreateInstIntrinsic(DataType::ANY, bcPc,
                                           RuntimeInterface::IntrinsicId::INTRINSIC_NEWOBJ_DYNRANGE_HANDLED);

    auto saveState = CreateSaveState(Opcode::SaveState, bcPc);
    AddInstruction(saveState);

    uint32_t constexpr FIXED_INPUTS = 2U;
    auto argsCount = bcInst->GetImm64();
    inst->ReserveInputs(argsCount + FIXED_INPUTS);
    inst->AllocateInputTypes(graph->GetAllocator(), argsCount + FIXED_INPUTS);

    inst->AppendInput(FindOrCreateConstant(argsCount - 2));
    inst->AddInputType(DataType::UINT16);

    for (int64_t i = 0; i < argsCount; ++i) {
        inst->AppendInput(GetDefinition(startReg + i));
        inst->AddInputType(DataType::ANY);
    }
    inst->AppendInput(saveState);
    inst->AddInputType(DataType::NO_TYPE);
    inst->SetFlag(inst_flags::ACC_WRITE);
    AddInstruction(inst);
    UpdateDefinitionAcc(inst);
}

void InstBuilder::BuildEcmaGetunmappedargs(const BytecodeInstruction *bcInst)
{
    // NOTE(pishin) support for inlined graph
    if (callerInst_ != nullptr) {
        failed_ = true;
        return;
    }
    auto graph = GetGraph();
    // If more parameters are passed to the function than in its description,
    // then the GC can move the "implicit" parameters.
    // So we remove SafePoint from the start block and check SaveState instruction between
    for (auto sp : graph->GetStartBlock()->AllInsts()) {
        if (sp->GetOpcode() != Opcode::SafePoint) {
            continue;
        }
        sp->ClearFlag(inst_flags::NO_DCE);
    }
    auto bb = GetCurrentBlock();
    // Conservative check that there is no GC call from beggining to the instruction
    if (bb->GetPredsBlocks().size() != 1 || bb->GetPredecessor(0) != graph->GetStartBlock()) {
        failed_ = true;
        return;
    }
    auto currInst = bb->GetLastInst();
    while (currInst != nullptr) {
        if (currInst->IsSaveState()) {
            failed_ = true;
            return;
        }
        currInst = currInst->GetPrev();
    }
    auto bcPc = GetPc(bcInst->GetAddress());
    auto inst =
        graph->CreateInstIntrinsic(DataType::ANY, bcPc, RuntimeInterface::IntrinsicId::INTRINSIC_GET_UNMAPPED_ARGS);

    auto saveState = CreateSaveState(Opcode::SaveState, bcPc);
    AddInstruction(saveState);

    // first input is actual num args, second save state
    uint32_t constexpr FIXED_INPUTS = 2U;
    inst->ReserveInputs(FIXED_INPUTS);
    inst->AllocateInputTypes(graph->GetAllocator(), FIXED_INPUTS);
    auto numArgsInst = graph->FindParameter(ParameterInst::DYNAMIC_NUM_ARGS);
    ASSERT(numArgsInst != nullptr);
    inst->AppendInput(numArgsInst);
    inst->AddInputType(DataType::UINT32);

    inst->AppendInput(saveState);
    inst->AddInputType(DataType::NO_TYPE);
    inst->SetFlag(inst_flags::ACC_WRITE);
    AddInstruction(inst);
    UpdateDefinitionAcc(inst);
}

}  // namespace ark::compiler

#include "ecmascript_inst_builder_gen.cpp"

namespace ark::compiler {

template <bool IS_ACC_READ>
void InstBuilder::BuildStGlobalVar(const BytecodeInstruction *bcInst, uint32_t typeId)
{
    auto pc = GetPc(bcInst->GetAddress());
    auto saveState = CreateSaveState(Opcode::SaveState, pc);
    auto getAddress =
        graph_->CreateInstGetGlobalVarAddress(DataType::REFERENCE, pc, GetEnvDefinition(CONST_POOL_IDX), saveState,
                                              TypeIdMixin {typeId, GetGraph()->GetMethod()}, 0);

    auto storeObject = graph_->CreateInstStoreObject(
        DataType::ANY, pc, getAddress, nullptr, TypeIdMixin {TypeIdMixin::MEM_DYN_GLOBAL_ID, GetGraph()->GetMethod()},
        nullptr, false, true);
    storeObject->SetObjectType(ObjectType::MEM_DYN_GLOBAL);
    Inst *storeVal = nullptr;
    if constexpr (IS_ACC_READ) {
        storeVal = GetDefinitionAcc();
    } else {
        storeVal = GetDefinition(bcInst->GetVReg(0));
    }

    storeObject->SetInput(1, storeVal);
    AddInstruction(saveState);
    AddInstruction(getAddress);
    AddInstruction(storeObject);
}

template <bool IS_ACC_WRITE>
void InstBuilder::BuildLdGlobalVar(const BytecodeInstruction *bcInst, uint32_t typeId)
{
    auto pc = GetPc(bcInst->GetAddress());
    auto saveState = CreateSaveState(Opcode::SaveState, pc);
    auto getAddress =
        graph_->CreateInstGetGlobalVarAddress(DataType::REFERENCE, pc, GetEnvDefinition(CONST_POOL_IDX), saveState,
                                              TypeIdMixin {typeId, GetGraph()->GetMethod()}, 0);

    auto loadObject = graph_->CreateInstLoadObject(
        DataType::ANY, pc, getAddress, TypeIdMixin {TypeIdMixin::MEM_DYN_GLOBAL_ID, GetGraph()->GetMethod()}, nullptr);
    loadObject->SetObjectType(ObjectType::MEM_DYN_GLOBAL);
    AddInstruction(saveState);
    AddInstruction(getAddress);
    AddInstruction(loadObject);

    if constexpr (IS_ACC_WRITE) {
        UpdateDefinitionAcc(loadObject);
    } else {
        UpdateDefinition(bcInst->GetVReg(0), loadObject);
    }
}

void InstBuilder::BuildStObjByIndex(const BytecodeInstruction *bcInst, uint64_t imm)
{
    auto pc = GetPc(bcInst->GetAddress());
    if (GetRuntime()->CanInlineLdStObjByIndex(bcInst, pc, methodProfile_)) {
        BuildLdStObjByIndex(bcInst, GetDefinition(bcInst->GetVReg(0)), imm, GetDefinitionAcc());
    } else {
        BuildEcma(bcInst);
    }
}

void InstBuilder::BuildLdObjByIndex(const BytecodeInstruction *bcInst, uint64_t imm)
{
    auto pc = GetPc(bcInst->GetAddress());
    if (GetRuntime()->CanInlineLdStObjByIndex(bcInst, pc, methodProfile_)) {
        BuildLdStObjByIndex(bcInst, GetDefinitionAcc(), imm, nullptr);
    } else {
        BuildEcma(bcInst);
    }
}

void InstBuilder::BuildLdStObjByIndex(const BytecodeInstruction *bcInst, Inst *objInst, uint64_t imm, Inst *storeDef)
{
    auto pc = GetPc(bcInst->GetAddress());
    auto checkObj = graph_->CreateInstObjByIndexCheck(DataType::ANY, pc, objInst, nullptr);

    auto loadObject =
        graph_->CreateInstLoadObject(DataType::REFERENCE, pc, checkObj,
                                     TypeIdMixin {TypeIdMixin::MEM_DYN_ELEMENTS_ID, GetGraph()->GetMethod()}, nullptr);
    loadObject->SetObjectType(ObjectType::MEM_DYN_ELEMENTS);

    auto [saveState, nullCheck, arrayLength, boundsCheck] = BuildChecksBeforeArray(pc, loadObject, false);
    ASSERT(saveState != nullptr && nullCheck == loadObject && arrayLength != nullptr && boundsCheck != nullptr);

    checkObj->SetInput(1, saveState);

    // Create instruction
    auto inst = graph_->CreateInstLoadArray(DataType::ANY, pc, nullCheck, boundsCheck);
    boundsCheck->SetInput(1, FindOrCreateConstant(imm));
    boundsCheck->SetFlag(inst_flags::CAN_DEOPTIMIZE);

    auto cmpInst = graph_->CreateInstCompare(
        DataType::BOOL, pc, inst, graph_->FindOrCreateConstant(DataType::Any(ark::coretypes::TaggedValue::VALUE_HOLE)),
        DataType::ANY, ConditionCode::CC_EQ);
    auto deoptInst = graph_->CreateInstDeoptimizeIf(pc, cmpInst, saveState, DeoptimizeType::HOLE);

    AddInstruction(saveState, checkObj, loadObject, arrayLength, boundsCheck, inst, cmpInst, deoptInst);

    if (storeDef != nullptr) {
        auto store = graph_->CreateInstStoreArray(DataType::ANY, pc, loadObject, boundsCheck, storeDef, true);
        AddInstruction(store);
    } else {
        UpdateDefinitionAcc(inst);
    }
}

template void InstBuilder::BuildStGlobalVar<true>(const BytecodeInstruction *bc_inst, uint32_t type_id);
template void InstBuilder::BuildStGlobalVar<false>(const BytecodeInstruction *bc_inst, uint32_t type_id);

template void InstBuilder::BuildLdGlobalVar<true>(const BytecodeInstruction *bc_inst, uint32_t type_id);
template void InstBuilder::BuildLdGlobalVar<false>(const BytecodeInstruction *bc_inst, uint32_t type_id);

}  // namespace ark::compiler
